﻿using System.IO;

namespace SharpDepend.Storage.DataFormats
{
    public class SimpleC2 : IDataFormat
    {
        const string TAB = "\t";

        public override void Load(CustomNode headNodeToLoadTo)
        {
            LoadScope(mFile, 0, headNodeToLoadTo);
        }

        public override void Save(CustomNode headNodeToSaveFrom)
        {
            WriteScopeStart(mFile, headNodeToSaveFrom, 0);
            WriteScopeContent(mFile, headNodeToSaveFrom, 0);
            WriteScopeEnd(mFile, 0);
        }

        #region READ

        void LoadScope(Stream stream, long position, CustomNode toNode)
        {
            stream.Position = position;

            // Find '['.
            long startIndex = FindNext(stream, (byte)'[');
            // Find ']'
            long endIndex = FindNext(stream, (byte)']');
            // Get the name between.
            toNode.Name = ReadRange(stream, startIndex, endIndex - 1);

            // Find '{'.
            long startNodeIndex = FindNext(stream, (byte)'{');

            back:

            // Find any charachter.
            byte c;
            //long endNodeIndex = FindNext(stream, (byte)'}', (byte)'[', out c);
            long endNodeIndex = FindAnyChar(stream, out c);

            // Read inner node.
            if (c == '[')
            {
                LoadScope(stream, endNodeIndex - 1, new CustomNode(toNode));
                goto back;
            }
            // Done with the node.
            else if (c == '}')
            {
                // DONE!
            }
            else // Begining of an property.
            {
                LoadProperty(stream, endNodeIndex - 1, toNode);
                goto back;
            }
        }

        void LoadProperty(Stream stream, long position, CustomNode toNode)
        {
            stream.Position = position;

            long endIndex = FindNext(stream, (byte)';');

            string property = ReadRange(stream, position, endIndex);

            int first = property.IndexOf('=');

            string name = property.Substring(0, first).TrimEnd();
            string value = property.Substring(first + 1, property.Length - (first + 2)).TrimStart();
            toNode.AddProperty(new Property(name, value));
        }

        private static long FindAnyChar(Stream stream, out byte c)
        {
            c = 0;
            long position = stream.Position;
            long length = stream.Length;
            while (position != length)
            {
                ++position;
                int b = stream.ReadByte();
                if (b != '\n' && b != '\r' && b != ' ' && b != '\t')
                {
                    c = (byte)b;
                    return position;
                }
            }

            return -1;
        }

        private static long FindNext(Stream stream, byte c)
        {
            long position = stream.Position;
            long length = stream.Length;
            while (position != length)
            {
                ++position;
                if (stream.ReadByte() == c)
                {
                    return position;
                }
            }

            return -1;
        }

        private static long FindNext(Stream stream, byte c1, byte c2, out byte cResult)
        {
            cResult = 0;
            byte c = 0;
            long position = stream.Position;
            long length = stream.Length;
            while (position != length)
            {
                ++position;
                c = (byte)stream.ReadByte();
                if (c == c1)
                {
                    cResult = c1;
                    return position;
                }
                else if (c == c2)
                {
                    cResult = c2;
                    return position;
                }
            }

            return -1;
        }

        private static string ReadRange(Stream stream, long startIndex, long endIndex)
        {
            int readLength = (int)(endIndex - startIndex);
            byte[] data = new byte[readLength];
            stream.Position = startIndex;
            stream.Read(data, 0, readLength);

            string readed = System.Text.Encoding.UTF8.GetString(data, 0, data.Length);

            return readed;
        }

        #endregion

        #region SAVE
        
        void Write(Stream stream, string data)
        {
            byte[] array = System.Text.Encoding.UTF8.GetBytes(data);

            stream.Write(array, 0, array.Length);
        }

        void WriteTabs(Stream stream, int subIndex)
        {
            for (int i = 0; i < subIndex; i++)
            {
                Write(stream, TAB);
            }
        }

        void WriteScopeStart(Stream stream, CustomNode node, int subIndex)
        {
            WriteTabs(stream, subIndex);
            Write(stream, "[");
            Write(stream, node.Name);
            Write(stream, "]\r\n");
            WriteTabs(stream, subIndex);
            Write(stream, "{\r\n");
        }

        private void WriteScopeContent(Stream stream, CustomNode node, int subIndex)
        {
            //WriteTabs(stream, subIndex + 1);

            // Skriver ut attributerna + värdet
            for (int i = 0; i < node.Properties.Count; i++)
            {
                WriteTabs(stream, subIndex + 1);
                Write(stream, node.Properties[i].Name);
                Write(stream, " = ");
                Write(stream, node.Properties[i].Value);
                Write(stream, ";\r\n");
            }

            // Skriver ut inner-scopes
            for (int i = 0; i < node.Nodes.Count; i++)
            {
                WriteScopeStart(stream, node.Nodes[i], subIndex + 1);
                WriteScopeContent(stream, node.Nodes[i], subIndex + 1);
                WriteScopeEnd(stream, subIndex + 1);
            }
        }

        private void WriteScopeEnd(Stream stream, int subIndex)
        {
            WriteTabs(stream, subIndex);
            Write(stream, "}\r\n");
        }

        #endregion
    }
}
